package com.nts.visionworks.core.mediainfo;

import java.lang.reflect.Method;

import org.slf4j.LoggerFactory;

import static java.util.Collections.singletonMap;

import com.sun.jna.FunctionMapper;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import com.sun.jna.WString;

/**
 * JNA 라이브러리를 이용해 MediaInfo 라이브러리를 로드하고 DLL의 메서드를 연결하여 메서드 실행을 중계한다.
 * @author Devcken
 */
public final class MediaInfoNativeAccess
{
	static
	{
		try
		{
			String osName = System.getProperty("os.name");
			
			// 운영체제 명칭를 가져와 Windows인지 MAC OS인지 판단한다.
			if (osName != null && !osName.toLowerCase().startsWith("windows") && !osName.toLowerCase().startsWith("mac"))
			{
				NativeLibrary.getInstance("zen");
			}
		}
		catch (LinkageError error)
		{
			LoggerFactory.getLogger(MediaInfoNativeAccess.class.getName()).warn("Failed to preload libzen");
		}
	}

	/**
	 * 
	 * @author Devcken
	 */
	interface MediaInfoDLL_Internal extends Library
	{
		MediaInfoDLL_Internal INSTANCE = (MediaInfoDLL_Internal)Native.loadLibrary("mediainfo", MediaInfoDLL_Internal.class, singletonMap(OPTION_FUNCTION_MAPPER, new FunctionMapper()
			{
				public String getFunctionName(NativeLibrary lib, Method method)
				{
					return "MediaInfo_" + method.getName();
				}
			}
		));

		/**
		 * 
		 * @return
		 */
		Pointer New();
		
		/**
		 * 
		 * @param Handle
		 */
		void Delete(Pointer Handle);

		/**
		 * 
		 * @param Handle
		 * @param file
		 * @return
		 */
		int Open(Pointer Handle, WString file);
		
		/**
		 * 
		 * @param Handle
		 */
		void Close(Pointer Handle);

		/**
		 * 
		 * @param Handle
		 * @return
		 */
		WString Inform(Pointer Handle);
		
		/**
		 * 
		 * @param Handle
		 * @param StreamType
		 * @param StreamNumber
		 * @param parameter
		 * @param informationType
		 * @param searchKind
		 * @return
		 */
		WString Get(Pointer Handle, int StreamType, int StreamNumber, WString parameter, int informationType, int searchKind);
		
		/**
		 * 
		 * @param Handle
		 * @param StreamType
		 * @param StreamNumber
		 * @param parameterIndex
		 * @param informationType
		 * @return
		 */
		WString GetI(Pointer Handle, int StreamType, int StreamNumber, int parameterIndex, int informationType);
		
		/**
		 * 
		 * @param Handle
		 * @param StreamType
		 * @param StreamNumber
		 * @return
		 */
		int Count_Get(Pointer Handle, int StreamType, int StreamNumber);

		/**
		 * 
		 * @param Handle
		 * @param option
		 * @param value
		 * @return
		 */
		WString Option(Pointer Handle, WString option, WString value);
	}
	
	/**
	 * 로드된 라이브러리(DLL)에 대한 핸들를 나타낸다.
	 */
	private Pointer _handle;

	/**
	 * 
	 * @param libraryPath 로드하고자 하는 DLL의 절대 경로를 설정한다.
	 */
	public MediaInfoNativeAccess(String libraryPath)
	{
		String jnaLibraryPath = System.getProperty("jna.library.path");
		
		if (jnaLibraryPath == null)
		{
			jnaLibraryPath = "";
		}
		
		if (jnaLibraryPath.indexOf(libraryPath) == -1)
		{
			jnaLibraryPath += ";" + libraryPath;
			
			System.setProperty("jna.library.path", jnaLibraryPath);
		}
	}

	/**
	 * 
	 */
	public void dispose()
	{
		if (this._handle == null)
		{
			throw new IllegalStateException();
		}
	
		MediaInfoDLL_Internal.INSTANCE.Delete(this._handle);
		
		this._handle = null;
	}

	/**
	 * 
	 */
	@Override
	protected void finalize() throws Throwable
	{
		if (this._handle != null)
			dispose();
	}

	/**
	 * Open a file and collect information about it (technical information and tags).
	 * 
	 * @param file full name of the file to open
	 * @return 1 if file was opened, 0 if file was not not opened
	 */
	public int Open(String File_Name)
	{
		this._handle = MediaInfoDLL_Internal.INSTANCE.New();
		
		return MediaInfoDLL_Internal.INSTANCE.Open(this._handle, new WString(File_Name));
	}

	/**
	 * Close a file opened before with Open().
	 * 
	 */
	public void Close()
	{
		MediaInfoDLL_Internal.INSTANCE.Close(this._handle);
	}

	/**
	 * Get all details about a file.
	 * 
	 * @return All details about a file in one string
	 */
	public String Inform()
	{
		return MediaInfoDLL_Internal.INSTANCE.Inform(this._handle).toString();
	}

	/**
	 * Get a piece of information about a file (parameter is a string).
	 * 
	 * @param StreamType Kind of Stream (general, video, audio...)
	 * @param StreamNumber Stream number in Kind of Stream (first, second...)
	 * @param parameter Parameter you are looking for in the Stream (Codec, width, bitrate...),
	 *            in string format ("Codec", "Width"...)
	 * @return a string about information you search, an empty string if there is a problem
	 */
	public String Get(StreamType StreamType, int StreamNumber, String parameter)
	{
		return Get(StreamType, StreamNumber, parameter, InformationType.Text, InformationType.Name);
	}

	/**
	 * Get a piece of information about a file (parameter is a string).
	 * 
	 * @param StreamType Kind of Stream (general, video, audio...)
	 * @param StreamNumber Stream number in Kind of Stream (first, second...)
	 * @param parameter Parameter you are looking for in the Stream (Codec, width, bitrate...),
	 *            in string format ("Codec", "Width"...)
	 * @param informationType Kind of information you want about the parameter (the text, the measure,
	 *            the help...)
	 * @param searchKind Where to look for the parameter
	 */
	public String Get(StreamType StreamType, int StreamNumber, String parameter, InformationType informationType)
	{
		return Get(StreamType, StreamNumber, parameter, informationType, InformationType.Name);
	}

	/**
	 * Get a piece of information about a file (parameter is a string).
	 * 
	 * @param StreamType Kind of Stream (general, video, audio...)
	 * @param StreamNumber Stream number in Kind of Stream (first, second...)
	 * @param parameter Parameter you are looking for in the Stream (Codec, width, bitrate...),
	 *            in string finformationTypeCodec", "Width"...)
	 * @param informationType Kind of information you want about the parameter (the text, the measure,
	 *            the help...)
	 * @param searchKind Where to look for the parameter
	 * @return a string about information you search, an empty string if there is a problem
	 */
	public String Get(StreamType StreamType, int StreamNumber, String parameter, InformationType informationType, InformationType searchType)
	{
		return MediaInfoDLL_Internal.INSTANCE.Get(this._handle, StreamType.ordinal(), StreamNumber, new WString(parameter), informationType.ordinal(), searchType.ordinal()).toString();
	}

	/**
	 * Get a piece of information about a file (parameter is an integer).
	 * 

	 * @param StreamType Kind of Stream (general, video, audio...)
	 * @param StreamNumber Stream number in Kind of Stream (first, second...)
	 * @param parameter Parameter you are looking for in the Stream (Codec, width, bitrate...),
	 *            in integer format (first parameter, second parameter...)
	 * @return a string about information you search, an empty string if there is a problem
	 */
	public String Get(StreamType StreamType, int StreamNumber, int parameterIndex)
	{
		return Get(StreamType, StreamNumber, parameterIndex, InformationType.Text);
	}

	/**
	 * Get a piece of information about a file (parameter is an integer).
	 * 

	 * @param StreamType Kind of Stream (general, video, audio...)
	 * @param StreamNumber Stream number in Kind of Stream (first, second...)
	 * @param parameter Parameter you are looking for in the Stream (Codec, width, bitrate...),
	 *            in integer format (first parameter, second parameter...)
	 * @param informationType Kind of information you want about the parameter (the text, the measure,
	 *            the help...)
	 * @return a string about information you search, an empty string if there is a problem
	 */
	public String Get(StreamType StreamType, int StreamNumber, int parameterIndex, InformationType informationType)
	{
		return MediaInfoDLL_Internal.INSTANCE.GetI(this._handle, StreamType.ordinal(), StreamNumber, parameterIndex, informationType.ordinal()).toString();
	}

	/**
	 * Count of Streams of a Stream kind (StreamNumber not filled), or count of piece of
	 * information in this Stream.
	 * 

	 * @param StreamType Kind of Stream (general, video, audio...)
	 * @return number of Streams of the given Stream kind
	 */
	public int Count_Get(StreamType StreamType)
	{
		return MediaInfoDLL_Internal.INSTANCE.Count_Get(this._handle, StreamType.ordinal(), -1);
	}

	/**
	 * Count of Streams of a Stream kind (StreamNumber not filled), or count of piece of
	 * information in this Stream.
	 * 
	 * @param StreamType Kind of Stream (general, video, audio...)
	 * @param StreamNumber Stream number in this kind of Stream (first, second...)
	 * @return number of Streams of the given Stream kind
	 */
	public int Count_Get(StreamType StreamType, int StreamNumber)
	{
		return MediaInfoDLL_Internal.INSTANCE.Count_Get(this._handle, StreamType.ordinal(), StreamNumber);
	}

	/**
	 * Configure or get information about MediaInfo.
	 * 
	 * @param Option The name of option
	 * @return Depends on the option: by default "" (nothing) means No, other means Yes
	 */
	public String Option(String Option)
	{
		return MediaInfoDLL_Internal.INSTANCE.Option(this._handle, new WString(Option), new WString("")).toString();
	}

	/**
	 * Configure or get information about MediaInfo.
	 * 
	 * @param Option The name of option
	 * @param Value The value of option
	 * @return Depends on the option: by default "" (nothing) means No, other means Yes
	 */
	public String Option(String Option, String Value)
	{
		return MediaInfoDLL_Internal.INSTANCE.Option(this._handle, new WString(Option), new WString(Value)).toString();
	}

	/**
	 * Configure or get information about MediaInfo (Static version).
	 * 
	 * @param Option The name of option
	 * @return Depends on the option: by default "" (nothing) means No, other means Yes
	 */
	public static String Option_Static(String Option)
	{
		return MediaInfoDLL_Internal.INSTANCE.Option(MediaInfoDLL_Internal.INSTANCE.New(), new WString(Option), new WString("")).toString();
	}

	/**
	 * Configure or get information about MediaInfo(Static version).
	 * 
	 * @param Option The name of option
	 * @param Value The value of option
	 * @return Depends on the option: by default "" (nothing) means No, other means Yes
	 */
	public static String Option_Static(String Option, String Value)
	{
		return MediaInfoDLL_Internal.INSTANCE.Option(MediaInfoDLL_Internal.INSTANCE.New(), new WString(Option), new WString(Value)).toString();
	}
}